﻿/*
	Copyright (c) 第三界主 2014
	代码使用MIT协议发布
*/

#include <wayland-client.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <linux/input.h>

#include "wayland-hello.h"


void pointer_enter(void *data, HPOINTER wl_pointer, uint32_t serial, HSURFACE surface, 
	wl_fixed_t surface_x, wl_fixed_t surface_y)
{
	wlGetRegistry()->s_pointer_surface = surface;
}

void pointer_leave(void *data, HPOINTER wl_pointer, uint32_t serial, HSURFACE surface)
{
	wlGetRegistry()->s_pointer_surface = NULL;
}

void pointer_motion(void *data, HPOINTER wl_pointer, uint32_t time, 
	wl_fixed_t surface_x, wl_fixed_t surface_y)
{

}

void pointer_button(void *data, HPOINTER wl_pointer, uint32_t serial, 
	uint32_t time, uint32_t button, uint32_t state)
{
	HSURFACE hSurface = wlGetRegistry()->s_pointer_surface;

	if (hSurface == NULL)
		return;

	if (BTN_LEFT == button && state == WL_POINTER_BUTTON_STATE_PRESSED)
	{
		HSHELLSURFACE hShellSurface = _wlGetSurfacePrivate(hSurface)->shell_surface;
		wl_shell_surface_move(hShellSurface, wlGetRegistry()->s_seat, serial);
		return;
	}
}

void pointer_axis(void *data, struct wl_pointer *wl_pointer, 
	uint32_t time, uint32_t axis, wl_fixed_t value)
{

}

struct wl_pointer_listener pointer_listener = {
	.enter = pointer_enter,
	.leave = pointer_leave,
	.motion = pointer_motion,
	.button = pointer_button,
	.axis = pointer_axis
};

void keyboard_keymap(LPVOID data, HKEYBOARD wl_keyboard, uint32_t format, 
	int32_t fd, uint32_t size)
{

}

void keyboard_enter(LPVOID data, HKEYBOARD wl_keyboard, uint32_t serial, 
	HSURFACE surface, struct wl_array *keys)
{
	printf("keyboard focus.\n");
}

void keyboard_leave(void *data, HKEYBOARD wl_keyboard, uint32_t serial, HSURFACE surface)
{

}

void keyboard_key(void *data, HKEYBOARD wl_keyboard,
	uint32_t serial,
	uint32_t time,
	uint32_t key,
	uint32_t state)
{
	if (KEY_F4 == key && WL_KEYBOARD_KEY_STATE_PRESSED == state)
	{
		exit(0);
	}
}

void keyboard_modifiers(void *data,
	HKEYBOARD wl_keyboard,
	uint32_t serial,
	uint32_t mods_depressed,
	uint32_t mods_latched,
	uint32_t mods_locked,
	uint32_t group)
{

}

struct wl_keyboard_listener keyboard_listener = {
	.keymap = keyboard_keymap, 
	.enter = keyboard_enter, 
	.leave = keyboard_leave, 
	.key = keyboard_key, 
	.modifiers = keyboard_modifiers
};

static void handle_ping(LPVOID data, HSHELLSURFACE shell_surface, uint32_t serial)
{
	wl_shell_surface_pong(shell_surface, serial);
}

static void handle_configure(LPVOID data, HSHELLSURFACE shell_surface, 
	uint32_t edges, int32_t width, int32_t height)
{

}

static void handle_popup_done(LPVOID data, HSHELLSURFACE shell_surface){

}

static const struct wl_shell_surface_listener shell_surface_listener = {
	.ping = handle_ping,
	.configure = handle_configure,
	.popup_done = handle_popup_done
};

static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) {
	wlGetRegistry()->shm_formats |= (1 << format);
}

struct wl_shm_listener shm_listenter = {
	shm_format
};

void registry_global(LPVOID data, HREGISTRY registry, uint32_t id, const char *interface, uint32_t version)
{
	if (strcmp(interface, "wl_compositor") == 0)
	{
		wlGetRegistry()->s_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
	}
	else if (strcmp(interface, "wl_shell") == 0)
	{
		wlGetRegistry()->s_shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
	}
	else if (strcmp(interface, "wl_shm") == 0)
	{
		wlGetRegistry()->s_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
		wl_shm_add_listener(wlGetRegistry()->s_shm, &shm_listenter, NULL);
	}
	else if (strcmp(interface, "wl_seat") == 0)
	{
		wlGetRegistry()->s_seat = wl_registry_bind(registry, id, &wl_seat_interface, 1);
		wlGetRegistry()->s_pointer = wl_seat_get_pointer(wlGetRegistry()->s_seat);
		wlGetRegistry()->s_keyboard = wl_seat_get_keyboard(wlGetRegistry()->s_seat);
	}
}

static void registry_global_remove(LPVOID data, HREGISTRY registry, uint32_t name)
{

}

static struct wl_registry_listener registry_listener = { 
	.global = registry_global,
	.global_remove = registry_global_remove
};

int wlInit()
{
	wlGetRegistry()->s_display = wl_display_connect(NULL);
	HREGISTRY registry = wl_display_get_registry(wlGetRegistry()->s_display);

	/* wayland的API是异步的，需要给registry设置回掉函数 */
	wl_registry_add_listener(registry, &registry_listener, NULL);

	/* 处理消息 */
	wl_display_roundtrip(wlGetRegistry()->s_display);
	wl_display_get_fd(wlGetRegistry()->s_display);

	/* 处理鼠标消息 */
	wl_pointer_add_listener(wlGetPointer(), &pointer_listener, NULL);
	wl_keyboard_add_listener(wlGetKeyboard(), &keyboard_listener, NULL);
}


HSURFACE wlCreateSurface(const char* title, int width, int height)
{
	/* wayland中，窗口被称作surface */
	HSURFACE surface = wl_compositor_create_surface(wlGetCompositor());

	/* shell_surface具有移动、改变大小、设置标题、最大化、最小化等功能 */
	HSHELLSURFACE shell_surface = wl_shell_get_shell_surface(wlGetShell(), surface);

	/* 窗口的私有数据 */
	LPSURFACEPRIVATE surface_priv = (LPSURFACEPRIVATE)malloc(sizeof(SURFACEPRIVATE));
	memset(surface_priv, 0, sizeof(SURFACEPRIVATE));
	surface_priv->surface = surface;
	surface_priv->shell_surface = shell_surface;
	surface_priv->width = width;
	surface_priv->height = height;

	wl_surface_set_user_data(surface, surface_priv);

	/* 设置响应函数 */
	wl_shell_surface_add_listener(shell_surface, &shell_surface_listener, NULL);
	wl_shell_surface_set_title(shell_surface, title);
	wl_shell_surface_set_toplevel(shell_surface);
	wl_surface_damage(surface, 0, 0, width, height);

	return surface;
}

LPSURFACEPRIVATE _wlGetSurfacePrivate( HSURFACE surface )
{
	return (LPSURFACEPRIVATE)wl_surface_get_user_data(surface);
}

void frame_listener_done(void *data, HCALLBACK callback, uint32_t time)
{
	HSURFACE surface = (HSURFACE)data;
	/* 每次都需要重新设置回调函数 */
	_wlSetFrameListener(surface);
	
	/* 绘制一帧 */
	_wlDrawFrame(surface, time);
}

static struct wl_callback_listener frame_listener = {
	.done = frame_listener_done
};

void _wlSetFrameListener(HSURFACE surface)
{
	HCALLBACK callback = wl_surface_frame(surface);
	wl_callback_add_listener(callback, &frame_listener, surface);
}

void _wlDrawFrame(HSURFACE surface, uint32_t time)
{
	PAINTSTRUCTURE ps;
	memset(&ps, 0, sizeof(ps));
	wlBeginPaint(surface, &ps);
	wlCallPaintProc(surface, &ps, time);
	wlEndPaint(surface, &ps);
}

void wlSetPaintProc(HSURFACE surface, REPAINTPROC surfaceproc)
{
	_wlGetSurfacePrivate(surface)->surfaceproc = surfaceproc;
	_wlSetFrameListener(surface);
	_wlDrawFrame(surface, 0);
}

void wlTerminate()
{
	wl_display_disconnect(wlGetDisplay());
}

void buffer_release(void *data, struct wl_buffer *buffer)
{
	LPPAINTBUFFER lpBuffer = data;
	lpBuffer->busy = 0;
}

static const struct wl_buffer_listener buffer_listener = {
	.release = buffer_release
};

void _wlCreatePaintStructure(int width, int height, LPPAINTBUFFER lpBuffer)
{
	int stride = width * 4;
	int size = stride * height;

	char filename[] = "/tmp/weston-shared-XXXXXX";
	int fd = mkstemp(filename);
	fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
	unlink(filename);
	ftruncate(fd, size);

	lpBuffer->busy = 0;

	lpBuffer->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	//lpBuffer->data == MAP_FAILED

	HSHMPOOL pool = wl_shm_create_pool(wlGetRegistry()->s_shm, fd, size);
	lpBuffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
	wl_buffer_add_listener(lpBuffer->buffer, &buffer_listener, lpBuffer);
	wl_shm_pool_destroy(pool);
	close(fd);
}

void wlBeginPaint(HSURFACE surface, LPPAINTSTRUCTURE lpPaint)
{
	LPSURFACEPRIVATE _priv = _wlGetSurfacePrivate(surface);
	if (!_priv->buffer[0].busy && _priv->buffer[0].buffer)
	{
		lpPaint->buffer = &_priv->buffer[0];
	}
	else if (!_priv->buffer[1].busy && _priv->buffer[1].buffer)
	{
		lpPaint->buffer = &_priv->buffer[1];
	}
	else
	{
		SIZE sz = wlGetSurfaceSize(surface);
		_wlCreatePaintStructure(sz.width, sz.height, &_priv->buffer[0]);
		lpPaint->buffer = &_priv->buffer[0];
	}
}

void wlEndPaint(HSURFACE surface, LPPAINTSTRUCTURE lpPaint)
{
	lpPaint->buffer->busy = 1;
	wl_surface_attach(surface, lpPaint->buffer->buffer, 0, 0);
	SIZE sz = wlGetSurfaceSize(surface);
	wl_surface_damage(surface, 0, 0, sz.width, sz.height);
	wl_surface_commit(surface);
}

SIZE wlGetSurfaceSize(HSURFACE surface)
{
	SIZE sz;
	sz.width = _wlGetSurfacePrivate(surface)->width;
	sz.height = _wlGetSurfacePrivate(surface)->height;
	return sz;
}

int wlDispatch()
{
	return wl_display_dispatch(wlGetDisplay());
}

void wlCallPaintProc(HSURFACE hSurface, LPPAINTSTRUCTURE lpPaint, uint32_t time)
{
	LPSURFACEPRIVATE _private = _wlGetSurfacePrivate(hSurface);
	if (_private->surfaceproc)
	{
		_private->surfaceproc(hSurface, lpPaint, time);
	}
}

LPWAYLANDREGISTRY wlGetRegistry()
{
	static WAYLANDREGISTRY registry;
	static int bInit = 1;
	if (bInit)
	{
		memset(&registry, 0, sizeof(registry));
		bInit = 0;
	}

	return &registry;
}
